using System;
using System.Collections.Generic;
using System.Text;

namespace Rapier.Kodachi.Firefox
{
    public class MozillaFile
    {
        int _headerIndex = -1;
        private MozillaNode _root = null;
        public MozillaNode Root
        {
            get { return _root; }
        }

        private List<string> _lines;
        protected List<string> Lines
        {
            get { return _lines; }
        }

        public MozillaFile(string[] lines)
        {
            if (lines == null)
            {
                throw new ArgumentNullException("string[] lines");
            }
            if (lines.Length == 0)
            {
                throw new ArgumentException("string[] lines");
            }
            _lines = new List<string>(lines);
            _root = MozillaNode.Create("Bookmarks");
            ResolveHeaderIndex();
            ResolveFoldersAndLinks(_root, ref _headerIndex, _lines);
        }

        private bool IsBeginFolderHTMLMarkup(string line)
        {
            return MozillaNode.BeginFolderHTMLMarkup.IsMatch(line);
        }
        private bool IsEndFolderHTMLMarkup(string line)
        {
            return MozillaNode.EndFolderHTMLMarkup.IsMatch(line);
        }

        private int ResolveHeaderIndex()
        {
            foreach (string line in _lines)
            {
                _headerIndex++;
                if (IsBeginFolderHTMLMarkup(line))
                    return _headerIndex;
            }
            throw new IndexOutOfRangeException();
        }

        private void ResolveFoldersAndLinks(MozillaNode parentNode, ref int lineNumber, List<string> lines)
        {
            while (lineNumber < lines.Count)
            {
                string line = lines[lineNumber++];
                if (IsEndFolderHTMLMarkup(line))
                    break;
                MozillaNode node = MozillaNode.Create(parentNode, line);
                if (node.IsFolder)
                    ResolveFoldersAndLinks(node, ref lineNumber, lines);
            }
        }

        public IEnumerable<MozillaNode> FindDuplicateNodes(MozillaNode matchNode) 
        {
            Predicate<MozillaNode> predicate =
                delegate(MozillaNode current) { return current.CompareTo(matchNode) == 0; };
            foreach (MozillaNode  node in this._root.FindAll(predicate))
            {
                yield return node;
            }
        }

        public int RemoveDuplicateLinks()
        {
            int removed = 0;
            List<MozillaNode> nodes = new List<MozillaNode>(this.GetAllLinkNodes(_root));
            foreach (MozillaNode node in nodes)
            {
                List<MozillaNode> copies = new List<MozillaNode>(FindDuplicateNodes(node));
                if (copies.Count>1)
                {
                    for (int i = 1; i < copies.Count; i++)
                    {
                        MozillaNode duplicateNode = copies[i];
                        MozillaNode.DeleteRelationShip(duplicateNode.Parent, duplicateNode);
                        removed++;
                    }   
                }
            }
            return removed;
        }

        public void Save(string fullyQualifiedFilename)
        {
            string newMozillaLinks = "";
            foreach (string s in Header)
            {
                newMozillaLinks += s + Environment.NewLine;
            }
            newMozillaLinks += "<DL><p>" + Environment.NewLine;

            FormatLines(_root, ref newMozillaLinks);

            

            System.IO.File.WriteAllText(fullyQualifiedFilename, newMozillaLinks);
        }

        private IEnumerable<MozillaNode> GetAllLinkNodes(MozillaNode fromNode)
        {
            Predicate<MozillaNode> predicate = delegate(MozillaNode current) { return current.IsLink; };
            return fromNode.FindAll(predicate);
        }

        public MozillaNode Find(MozillaNode matchNode)
        {
            Predicate<MozillaNode> predicate =
                delegate(MozillaNode current) { return current.IsLink && current.CompareTo(matchNode) == 0; };
            foreach (MozillaNode node in _root.FindAll(predicate))
            {
                return node;
            }
            return null;
        }

        public IEnumerable<string> Header
        {
            get
            {
                foreach (string line in _lines)
                {
                    if (IsBeginFolderHTMLMarkup(line))
                        yield break;
                    else
                        yield return line;
                }
            }
        }

        private void FormatLines(MozillaNode folder, ref string lines)
        {
            if (folder.Parent != null)
                lines += folder.Line + "\n" + "<DL><p>" + Environment.NewLine;
            foreach (MozillaNode node in folder.GetChildren())
            {
                if (node.IsFolder)
                {
                    FormatLines(node, ref lines);
                }
                else
                    lines += node.Line + Environment.NewLine;
            }
            lines += "</DL><p>" + Environment.NewLine;
        }

        private int _numberOfNewLinksFound = 0;
        public int NumberOfNewLinksFound
        {
            get { return _numberOfNewLinksFound; } 
        }

        private void FindNewNodes(MozillaNode folder, List<MozillaNode> newNodes)
        {
            foreach (MozillaNode child in folder.GetChildren())
            {
                if (child.IsFolder)
                {
                    FindNewNodes(child, newNodes);
                }
                else
                {
                    if (Find(child) == null)
                    {
                        newNodes.Add(child);                        
                    }
                }
            }
        }

        public void Merge(MozillaFile other) 
        {
            List<MozillaNode> newNodes = new List<MozillaNode>();
            _numberOfNewLinksFound = 0;
            FindNewNodes(other._root, newNodes);
            MoveNodes(_root, newNodes);
            _numberOfNewLinksFound = newNodes.Count;
        }

        private static void MoveNodes(MozillaNode toNode, List<MozillaNode> nodes)
        {
            foreach (MozillaNode node in nodes)
            {
                MozillaNode.DeleteRelationShip(node.Parent, node);
                MozillaNode.SetRelationShip(toNode, node);
            }
        }


    }
}
